TinyML:

Carros de lego,
microcontroladores e
visão computacional

Thiago Pires | IBM

Thiago Pires

Formação:

  • Estatística (UERJ)
  • MSc. Epidemiologia (ENSP/FIOCRUZ)
  • DSc. Engenharia Biomédica (COPPE/UFRJ)


Experiência profissional:

  • Pesquisador (FIOCRUZ)
  • Professor (UFF)
  • Estatístico (FGV)
  • Cientista de Dados (CIO/IBM)

TinyML

É uma abreviação para Tiny Machine Learning. Refere-se a uma abordagem de aplicação de modelos de aprendizado de máquina em dispositivos de recurso computacional limitado, como microcontroladores, microprocessadores e sistemas embarcados.

Microcontroladores

Microcontroladores

É um pequeno computador num único circuito integrado


  • Contendo um núcleo de processador
  • Memória
  • Periféricos programáveis de entrada e saída.
  • WIFI e Bluetooth.

ESP32 é uma série de microcontroladores de baixo custo e baixo consumo de energia.

ATOM Lite

  • G22, G19, etc: General Purpose Input/Output (GPIO) são portas programáveis de entrada e saída de dados.
  • I2C, SPI: Protocolos de comunicação.
  • GND, 3V3, 5V: Energia.
  • WIFI, Bluetooth
  • Infravermelho

ATOM Lite da M5Stack

Protocolos de comunicação

I2C

O protocolo I2C (Inter-Integrated Circuit) é uma das interfaces de comunicação serial mais antigas e amplamente usadas no mundo da eletrônica. Sua história remonta à década de 1980, quando foi desenvolvido pela Philips

sequenceDiagram
    participant Master as Microcontrolador
    participant Slave as Periférico

    Master->>Slave: Microcontrolador inicia comunicação
    note over Master,Slave: SDA muda de alta para baixa voltagem antes do SCL
    Master->>Slave: Envio do endereço do periférico (7 bits)
    note over Master,Slave: Inclusão R/W bit (0 para escrita)
    Slave->>Master: Acknowledge (ACK)
    note over Slave,Master: Reconhecimento do periférico com um bit
    Master->>Slave: Envio de dados
    note over Master,Slave: 8 bits
    Slave->>Master: Acknowledge (ACK)
    note over Slave,Master: Periférico reconhece
    Master-->>Slave: Condição de parada
    note over Slave,Master: SDA muda de baixa para alta voltagem após o SCL

Protocolos de comunicação

UART

O protocolo UART (Universal Asynchronous Receiver/Transmitter)

sequenceDiagram
    participant DispositivoA as Dispositivo A (Transmissor)
    participant DispositivoB as Dispositivo B (Receptor)

    DispositivoA->>DispositivoB: Início da Comunicação UART
    DispositivoA->>DispositivoB: TX para o RX
    note over DispositivoA, DispositivoB: TX de um dispositivo <br> transmite para o RX do outro
    DispositivoA-->>DispositivoB: Fim da Comunicação UART

Linguagens de programação para o ESP32


  • C, C++
  • Python (Micropython)
  • Rust
  • Go (TinyGo)
  • Javascript (Espruino)
  • NuttX (SO)


Lego Mindstorms agora aceita Python

Lego Technic

LEGO Technic é um tema de produtos LEGO voltada para a criação de modelos mais complexos, com o recurso a blocos e peças mais sofisticados do que os das linhas básica e temáticas de LEGO

Peças do Lego Technic

Triciclo Reverso

Controle remoto via socket

Esquema do controle remoto

Controle remoto via socket


atom_motion.py
import machine
import struct
import network
import socket
import neopixel
import utime

# setup uart
uart = machine.UART(1, tx=19, rx=22)
uart.init(115200, bits=8, parity=None, stop=1)

# setup led
np = neopixel.NeoPixel(machine.Pin(27), 1)

# setup servo and motor
sda_pin = machine.Pin(25)
scl_pin = machine.Pin(21)

i2c = machine.I2C(0, sda=sda_pin, scl=scl_pin, freq=400000)
devices = i2c.scan()

device = [i for i in devices if hex(i) == '0x38'][0]

def set_speed(speed):
    buf = bytearray(1)
    struct.pack_into('b', buf, 0, speed)
    i2c.writeto_mem(device, 0, buf)

def set_angle(angle):
    buf = bytearray(1)
    struct.pack_into('b', buf, 0, angle)
    i2c.writeto_mem(device, 2, buf)

def set_direction(x):
    if x > 200:
        set_angle(65)
    elif x < 50:
        set_angle(115)
    else:
        set_angle(90)
        
def set_run(x):
    if x > 200:
        set_speed(127)
    elif x < 50:
        set_speed(0)
    else:
        set_speed(86)

# create access-point
ap = network.WLAN(network.AP_IF)
ap.config(essid='ATOM-MOTION')
ap.config(max_clients=10)
ap.active(True)

# create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('', 12000))

while True:
    
    try:
        status = int(uart.read(1).decode())
        if status == 1:
            np[0] = (0,0,255)
            np.write()
        elif status == 0:
            np[0] = (0, 255, 0)
            np.write()
            direction, address_client = server.recvfrom(2048)
            out = struct.unpack('BBB', direction)
            set_direction(out[0])
            set_run(out[1])
            print(out)
        else:
            np[0] = (255, 0, 0)
            np.write()
        print(status)
    except:
        pass
    
    utime.sleep_ms(500)
joystick.py
import machine
import struct
import network
import socket
import neopixel
import time

# setup led
np = neopixel.NeoPixel(machine.Pin(27), 1)

# setup joystick
sda_pin = machine.Pin(26)
scl_pin = machine.Pin(32)

i2c = machine.I2C(0, sda=sda_pin, scl=scl_pin, freq=400000)
devices = i2c.scan()
device = [i for i in devices if hex(i) == '0x52'][0]

# connect access-point
def ap_connect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network...')
        wlan.connect('ATOM-MOTION')
        while not wlan.isconnected():
            pass
    print('network config', wlan.ifconfig())

ap_connect()

# create client socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# green led
np[0] = (0,255,0)
np.write()

while True:
    client.sendto(i2c.readfrom(device, 3), ('192.168.4.1', 12000))
    time.sleep(1)

Controle por visão computacional

UnitV

  • Dual-Core 64-bit RISC-V
  • K210 é um chip de computação de borda bem poderoso, projetado para reconhecimento visual

Esquema de controle por visão

Treinamento do modelo

Dados

  • 600 imagens para treino
  • 300 segue reto
  • 300 vire a esquerda

MaixHub

Plataforma online para treinamento do modelo

Plataforma MaixHub
  • Image Augmentation: Rotação, Blur
  • Transfer learning do modelo mobilenet_0.5
  • Gera um modelo .kmodel

Controle por visão computacional


unitv_predict.py
import sensor, image, lcd, time
import KPU as kpu
from machine import UART
from fpioa_manager import fm

input_size = (224, 224)
lcd_rotation=0
labels = [1, 0]

# setup uart
fm.register(34, fm.fpioa.UART1_TX)
fm.register(35, fm.fpioa.UART1_RX)
uart = UART(UART.UART1, 115200, 8, 0, 0, timeout=1000, read_buf_len=1)

# setup sensor
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing(input_size)
sensor.skip_frames(time = 2000)

clock = time.clock()

try:
    task = None
    task = kpu.load("/sd/model-91412.kmodel")
    while(True):
        img = sensor.snapshot()
        t = time.ticks_ms()
        fmap = kpu.forward(task, img)
        t = time.ticks_ms() - t
        plist=fmap[:]
        pmax=max(plist)
        if pmax > .8:
            max_index=plist.index(pmax)
            out = labels[max_index]
            print(out, pmax)
            uart.write(str(out))
        lcd.display(img)
except Exception as e:
    print(e)
atom_motion_predict.py
import machine
import struct
import neopixel
import utime

# setup uart
uart = machine.UART(1, tx=19, rx=22)
uart.init(115200, bits=8, parity=0, stop=0)

# setup led
np = neopixel.NeoPixel(machine.Pin(27), 1)

# setup servo and motor
sda_pin = machine.Pin(25)
scl_pin = machine.Pin(21)

i2c = machine.I2C(0, sda=sda_pin, scl=scl_pin, freq=400000)
devices = i2c.scan()

device = [i for i in devices if hex(i) == '0x38'][0]

def set_speed(speed):
    buf = bytearray(1)
    struct.pack_into('b', buf, 0, speed)
    i2c.writeto_mem(device, 0, buf)

def set_angle(angle):
    buf = bytearray(1)
    struct.pack_into('b', buf, 0, angle)
    i2c.writeto_mem(device, 2, buf)

def set_direction(x):
    if x == 0:
        set_angle(90)
    elif x == 1:
        set_angle(75)
    else:
        set_angle(105)
        
def set_run(x):
    if x > 200:
        set_speed(127)
    elif x < 50:
        set_speed(0)
    else:
        set_speed(86)
        
while True:
    try:
        out = uart.read(1)
        print(out.decode())
        
        set_direction(int(out))
        set_speed(99)
    except:
        pass

Tensorflow


model.py
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential

num_classes = len(class_names)

model = Sequential([
  layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  layers.Resizing(IMG_SIZE, IMG_SIZE),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

tf.saved_model.save(model, 'model')

converter = tf.lite.TFLiteConverter.from_saved_model('model')
tflite_model = converter.convert()

with open('model.tflite', 'wb') as f:
    f.write(tflite_model)


converter.sh
./ncc compile model.tflite model.kmodel -i tflite -t k210 --dataset images

http://ibm.biz/rte2023nps-cps